import CollisionUnit from "../CollisionUnit";
import Direction from "../Direction";
import TankControl, { TankAction, Keyboard2Action } from "./TankControl";
import TankBullet from "./TankBullet";
import CollisionGroup from "../CollisionGroup";

const { ccclass, property } = cc._decorator;

@ccclass
export default class Tank extends CollisionUnit {

    @property(cc.Sprite)
    tank: cc.Sprite = null;

    @property({
        type: cc.Texture2D
    })
    bullet: cc.Texture2D = null;

    @property({
        type: cc.Texture2D,
    })
    texture: cc.Texture2D = null;

    @property({
        type: cc.Enum(cc.macro.KEY)
    })
    goUpKey: cc.macro.KEY = cc.macro.KEY.w;

    @property({
        type: cc.Enum(cc.macro.KEY)
    })
    goRightKey: cc.macro.KEY = cc.macro.KEY.d;

    @property({
        type: cc.Enum(cc.macro.KEY)
    })
    goDownKey: cc.macro.KEY = cc.macro.KEY.s;

    @property({
        type: cc.Enum(cc.macro.KEY)
    })
    goLeftKey: cc.macro.KEY = cc.macro.KEY.a;

    @property({
        type: cc.Enum(cc.macro.KEY)
    })
    shotKey: cc.macro.KEY = cc.macro.KEY.space;

    @property({
        type: cc.Integer,
        min: 0,
    })
    initialLevel: number = 0;

    @property({
        type: cc.Integer,
        min: 0,
    })
    maxLevel: number = 0;

    @property({
        type: cc.Enum(Direction),
    })
    initialDirection: Direction = Direction.up;

    private static allTanks: Tank[] = [];
    private static add(tank: Tank): number {
        const id = this.allTanks.length;
        this.allTanks.push(tank);
        return id;
    }
    private static remove(tankId: number) {
        this.allTanks[tankId] = null;
    }
    private static get(tankId: number): Tank {
        return this.allTanks[tankId];
    }

    private static defaultBulletTexture2d: cc.Texture2D;
    private static fetchDefaultBulletSpriteReq: Promise<cc.Texture2D>;
    private static async getDefaultBulletTexture2d() {

        if (!!this.defaultBulletTexture2d) {
            return this.defaultBulletTexture2d;
        } else if (!!this.fetchDefaultBulletSpriteReq) {
            return this.fetchDefaultBulletSpriteReq;
        }

        this.fetchDefaultBulletSpriteReq = new Promise((resolve, reject) => {
            cc.loader.loadRes("bullet/player1", (err, defaultBulletSprite) => {
                this.fetchDefaultBulletSpriteReq = null;

                if (!!defaultBulletSprite) {
                    this.defaultBulletTexture2d = defaultBulletSprite;
                    resolve(defaultBulletSprite);
                } else {
                    reject(err);
                }
            });
        });

        return this.fetchDefaultBulletSpriteReq;

    }

    public controller: TankControl;
    private tankId: number;
    private tankLevel: number = 0;

    private prevDirection: Direction;

    private shotWaitedTimes: number;
    private prevShotStatus: boolean;

    constructor() {
        super();
        this.tankId = Tank.add(this);
    }

    public get speed(): number {
        return 0.37 * this.tankLevel + 2;
        // return 19 * this.tankLevel + 77;
    }
    private get shotNeedWaitTime(): number {
        return 30 - 20 * this.tankLevel / this.maxLevel;
    }

    start() {
        this.prevDirection = null;
        this.shotWaitedTimes = 0;
        this.prevShotStatus = false;

        this.getComponent(cc.PhysicsBoxCollider).tag = this.tankId;

        this.controller = TankControl.regiter(
            this,
            new Keyboard2Action(
                this.goUpKey,
                this.goRightKey,
                this.goDownKey,
                this.goLeftKey,
                this.shotKey
            ),
            this.initialDirection
        );

        this.setLevel(this.initialLevel);
    }

    public getId() {
        return this.tankId;
    }
    public getLevel() {
        return this.tankLevel;
    }
    public getDirection(): Direction {
        return this.controller.getDirection();
    }
    public addLevel(count: number = 1) {
        this.setLevel(count + this.tankLevel);
    }
    public subLevel(count: number = 1) {
        this.setLevel(this.tankLevel - count);
    }
    private setLevel(level: number) {
        const prevLevel = this.tankLevel;

        this.tankLevel = 0 < level ?
            (
                this.maxLevel >= level ?
                    level :
                    this.maxLevel
            ) :
            level;

        if (0 > this.tankLevel) {
            this.node.destroy();
        } else if (prevLevel !== this.tankLevel) {
            this.renderPreference();
        }
    }

    onBeginContact(_, __, other: cc.PhysicsBoxCollider) {
        switch (other.node.group) {
            case CollisionGroup.bullet:
                if (this.getId() !== other.tag) {
                    const otherTank = Tank.get(other.tag);
                    if (this.node.group !== otherTank.node.group) {
                        this.shotten(other.tag);
                    }
                }
                return;
            // case CollisionGroup.player:
            // case CollisionGroup.enemy:
            // case CollisionGroup.wall:
            //     return;
            default:
                return;
        }
    }

    public shotten(shotterTankId: number) {
        const shotter = Tank.get(shotterTankId);
        this.subLevel(1 + shotter.getLevel());
    }
    public shot() {
        if (
            !this.prevShotStatus ||
            (++this.shotWaitedTimes) >= this.shotNeedWaitTime
        ) {
            this.shotWaitedTimes = 0;
            TankBullet.create(this);
        }
    }
    public async getBulletTexture2d() {
        return this.bullet || Tank.getDefaultBulletTexture2d();
    }

    private renderPreference(direction?: Direction) {
        const frame = new cc.SpriteFrame(this.texture);
        direction = direction || this.getDirection();
        frame.setRect(cc.rect(28 * this.tankLevel, 28 * direction, 28, 28));
        this.tank.spriteFrame = frame;
    }
    update() {
        const controller = this.controller;
        if (controller.isMoving()) {
            const x = controller.getPosX();
            const y = controller.getPosY();
            const node = this.node;
            if (x !== node.x || y !== node.y) {
                node.setPosition(x, y);
            }
        }

        const nowDirection = controller.getDirection();
        if (this.prevDirection !== nowDirection) {
            this.renderPreference(nowDirection);
            this.prevDirection = nowDirection;
        }

        const isShotting = controller.isShotting;
        if (isShotting) {
            this.shot();
        }
        this.prevShotStatus = isShotting;
    }

    // // LIFE-CYCLE CALLBACKS:

    onDestroy() {
        Tank.remove(this.tankId);
        this.unscheduleAllCallbacks();
    }

    onLoad() {
        const physicsManager = cc.director.getPhysicsManager();
        physicsManager.enabled = true;
    }

}
